<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>飞机大战</title>
  <link rel="icon" href="./img/飞机.png">
  <style>
    * {
      margin: 0;
      padding: 0;
    }

    body {
      background-color: #000;
    }

    #canvas {
      border: 2px solid pink;
    }

    #stage {
      width: 480px;
      height: 700px;
      margin: 0 auto;
    }
  </style>
</head>

<body>
  <div id="stage">
    <canvas id="canvas" width="480px" height="700px"></canvas>
  </div>

  <script>
    const canvas = document.querySelector('#canvas')
    // 利用获取到的画布初始化一个2D的画笔
    const context = canvas.getContext('2d');
    // 加载背景图片 这个过程是异步的
    const bg = new Image();
    bg.src = 'img/background.png'

    // 加载LOGO
    const copyright = new Image();
    copyright.src = 'img/shoot_copyright.png'

    // 初始化四张飞机大战加载图
    const loading_frame = new Image();
    loading_frame.src = 'img/game_loading.png'
    // const loading_frame = []
    // loading_frame[0] = new Image();
    // loading_frame[0].src = 'img/game_loading.png'
    // loading_frame[1] = new Image();
    // loading_frame[1].src = 'img/game_loading2.png'
    // loading_frame[2] = new Image();
    // loading_frame[2].src = 'img/game_loading3.png'
    // loading_frame[3] = new Image();
    // loading_frame[3].src = 'img/game_loading4.png'

    // 初始化个人控制飞机的图片
    const hero_frame = { live: [], death: [] };
    hero_frame.live[0] = new Image();
    hero_frame.live[0].src = 'img/me1.png'
    hero_frame.live[1] = new Image();
    hero_frame.live[1].src = 'img/me2.png'
    hero_frame.death[0] = new Image();
    hero_frame.death[0].src = 'img/me_destroy_1.png'
    hero_frame.death[1] = new Image();
    hero_frame.death[1].src = 'img/me_destroy_2.png'
    hero_frame.death[2] = new Image();
    hero_frame.death[2].src = 'img/me_destroy_3.png'
    hero_frame.death[3] = new Image();
    hero_frame.death[3].src = 'img/me_destroy_4.png'

    // 初始化子弹图片
    const b = new Image();
    b.src = 'img/bullet1.png'

    // 初始化敌机图片
    const e1 = {
      live: [],
      death: []
    }
    e1.live[0] = new Image();
    e1.live[0].src = 'img/enemy1.png'
    e1.death[0] = new Image();
    e1.death[0].src = 'img/enemy1_down1.png'
    e1.death[1] = new Image();
    e1.death[1].src = 'img/enemy1_down2.png'
    e1.death[2] = new Image();
    e1.death[2].src = 'img/enemy1_down3.png'
    e1.death[3] = new Image();
    e1.death[3].src = 'img/enemy1_down4.png'

    const e2 = {
      live: [],
      death: []
    }
    e2.live[0] = new Image();
    e2.live[0].src = 'img/enemy2.png'
    e2.death[0] = new Image();
    e2.death[0].src = 'img/enemy2_down1.png'
    e2.death[1] = new Image();
    e2.death[1].src = 'img/enemy2_down2.png'
    e2.death[2] = new Image();
    e2.death[2].src = 'img/enemy2_down3.png'
    e2.death[3] = new Image();
    e2.death[3].src = 'img/enemy2_down4.png'


    const e3 = {
      live: [],
      death: []
    }
    e3.live[0] = new Image();
    e3.live[0].src = 'img/enemy3_n1.png'
    e3.live[1] = new Image();
    e3.live[1].src = 'img/enemy3_n2.png'
    e3.death[0] = new Image();
    e3.death[0].src = 'img/enemy3_down1.png'
    e3.death[1] = new Image();
    e3.death[1].src = 'img/enemy3_down2.png'
    e3.death[2] = new Image();
    e3.death[2].src = 'img/enemy3_down3.png'
    e3.death[3] = new Image();
    e3.death[3].src = 'img/enemy3_down4.png'
    e3.death[4] = new Image();
    e3.death[4].src = 'img/enemy3_down5.png'
    e3.death[5] = new Image();
    e3.death[5].src = 'img/enemy3_down6.png'

    // 初始化暂停图片
    const pause = new Image();
    pause.src = 'img/game_pause.png'

    // 定义游戏状态
    const START = 0;
    const STARTING = 1;
    const RUNNING = 2;
    const PAUSE = 3;
    const END = 4;

    // canvas画布绘制 bg 对象的左上角坐标
    // let x = 0;
    // let y = 0;
    // let x2 = 0;
    // let y2 = -700;

    // 创建一个天空类
    class Sky {
      constructor(config) {
        // 静态属性
        // console.log(config);
        this.bg = config.bg;
        this.width = config.width;
        this.height = config.height;

        this.x1 = 0
        this.y1 = 0;
        this.x2 = 0;
        this.y2 = -this.height
        this.speed = config.speed
        // 最后更新时间
        this.lastTime = new Date().getTime()
      }
      // 动态方法

      // 判断这个时间段天空是否需要移动
      // 拿到当前的时间节点 再 拿到一个历史时间
      // 当前时间 - 速度时间 如果大于 历史事件 那么需要移动
      // 否则不需要移动
      judge() {
        let currentTime = new Date().getTime();
        if (currentTime - this.lastTime > this.speed) {
          this.y1++;
          this.y2++;
          this.lastTime = currentTime
        }
        if (this.y2 === 0) {
          this.y1 = 0;
          this.y2 = -this.height;
        }
      }
      // 绘图方法
      paint(context) {
        context.drawImage(this.bg, this.x1, this.y1, this.width, this.height);
        context.drawImage(this.bg, this.x2, this.y2, this.width, this.height);
        // 当第二张背景滚到坐标0；让两张图片回到初始状态以实现无缝连接
        // if (this.y2 === 0) {
        //   this.y1 = 0;
        //   this.y2 = -this.height;
        // }
      }
    }
    // 天空类配置项
    const SKY = {
      bg: bg,
      width: 480,
      height: 700,
      speed: 10
    }

    // 创建一个游戏加载类
    class Loading {
      constructor(config) {
        this.frame = config.frame
        this.frameIndex = 0;
        this.width = config.width;
        this.height = config.height;
        this.x = config.x;
        this.y = config.y;
        this.speed = config.speed;
        this.lastTime = new Date().getTime();
      }
      judge() {
        // const currentTime = new Date().getTime()
        // if (currentTime - this.lastTime > this.speed) {
        //   this.frameIndex++;
        //   if (this.frameIndex > 0) {
        //     state = RUNNING
        //   }
        //   this.lastTime = currentTime
        // }
        setTimeout(() => {
          state = RUNNING
        }, 500)
      }
      paint(context) {
        context.drawImage(this.frame, this.x, this.y)
      }
    }
    // 加载页面配置项
    const LOADING = {
      frame: loading_frame,
      width: 480,
      height: 110,
      x: 0,
      y: 700 - 116,
      speed: 1000
    }

    // 创建一个 个人战机类
    class Hero {
      constructor(config) {
        this.width = config.width;
        this.height = config.height;
        this.x = (480 - config.width) / 2;
        this.y = 700 - config.height;
        this.frame = config.frame;
        this.frameLiveIndex = 0;
        this.frameDeathIndex = 0;
        this.lastTime = new Date().getTime()
        this.speed = config.speed
        // 当前展示的图片
        this.img = null;
        this.live = true;
        // 子弹上次射击的时间
        this.lastShootTime = new Date().getTime();
        // 子弹射击的频率
        this.shootInterval = 150;
        // 弹夹
        this.bulletList = [];
        this.destroy = false;
      }
      judge() {
        const currentTime = new Date().getTime();
        if (currentTime - this.lastTime > this.speed) {
          if (this.live) {
            this.img = this.frame.live[this.frameLiveIndex++ % this.frame.live.length]
          } else {
            this.img = this.frame.death[this.frameDeathIndex++];
            if (this.frameDeathIndex === this.frame.death.length) {
              this.destroy = true;
            }
          }
          this.lastTime = currentTime
        }
      }
      paint(context) {
        // this.img = this.frame.live[0]
        context.drawImage(this.img, this.x, this.y)
      }
      // 战机射击子弹
      shoot() {
        // 获取当前时间
        const currentTime = new Date().getTime();
        if (currentTime - this.lastShootTime > this.shootInterval) {
          // 飞机头部初始化一个子弹对象
          let bullet = new Bullet(BULLET, this.x + this.width / 2 - BULLET.width / 2, this.y - BULLET.height);
          // 飞机认领这个子弹
          this.bulletList.push(bullet)
          // 在网页绘制一个子弹对象
          bullet.paint(context)
          // 更新飞机射击时间
          this.lastShootTime = currentTime
        }
      }
      // 与敌机碰撞
      collide() {
        // 碰到即死
        this.live = false;
      }
    }
    // 个人控制战机配置项
    const HERO = {
      frame: hero_frame,
      width: 102,
      height: 126,
      // x: 0,
      // y: 0,
      speed: 100
    }

    // 新建子弹类
    class Bullet {
      constructor(config, x, y) {
        this.img = config.img;
        this.width = config.width;
        this.height = config.height;
        this.x = x
        this.y = y
        this.destroy = false;
      }
      move() {
        this.y -= 3;
      }
      paint(context) {
        context.drawImage(this.img, this.x, this.y)
      }
      // 子弹超出画布
      outOfBounds() {
        // 返回为真则销毁子弹
        return this.y < -this.height;
      }
      // 子弹碰到敌机销毁
      collide() {
        this.destroy = true
      }
    }
    // 子弹配置项
    const BULLET = {
      img: b,
      width: 5,
      height: 11
    }

    // 新建敌机类
    class Enemy {
      constructor(config) {
        this.type = config.type;
        this.width = config.width;
        this.height = config.height;
        // 敌机初始化位置
        this.x = Math.floor(Math.random() * (480 - config.width))
        this.y = -config.height;
        // 敌机生命
        this.life = config.life;
        // 敌机分数
        this.score = config.score;
        // 敌机图库
        this.frame = config.frame;
        // 此时此刻展示的图片
        this.img = this.frame.live[0];
        // 活着的标识
        this.live = true;
        // 敌机速度
        // this.minSpeed = config.minSpeed;
        // this.maxSpeed = config.maxSpeed;
        this.speed = Math.floor(Math.random() * (config.minSpeed - config.maxSpeed + 1)) + config.maxSpeed
        //最后时间标识
        this.lastTime = new Date().getTime()
        // 死亡下标
        this.deathIndex = 0;
        // 动画播放完毕彻底销毁敌机
        this.destroyed = false;
      }
      // 判断是否需要渲染 / 怎么渲染 / 是否移动
      move() {
        const currentTime = new Date().getTime();
        if (currentTime - this.lastTime >= this.speed) {
          // 如果敌机live为true， 那么播放活着的图片地址
          if (this.live) {
            this.img = this.frame.live[0];
            this.y++;

            // 修正上一次时间
            // this.lastTime = currentTime
          } else {
            this.img = this.frame.death[this.deathIndex++]
            // 死亡动画播放完毕操彻底销毁
            if (this.deathIndex === this.frame.death.length) {
              this.destroyed = true
            }
          }
          // 修正上一次时间
          this.lastTime = currentTime;
        }
      }
      // 渲染敌机
      paint() {
        context.drawImage(this.img, this.x, this.y)
      }
      // 敌机飞出边界销毁
      outOfBounds() {
        if (this.y > 700) {
          return true
        }
      }
      // 敌机是否撞到其他物体（子弹 / hero）
      hit(o) {
        // 其他物体的左边
        let ol = o.x;
        // 其他物体的右边
        let or = o.x + o.width;
        // 其他物体的顶边
        let ot = o.y;
        // 其他物体的底边
        let ob = o.y + o.height;
        // 敌机的左边
        let el = this.x;
        // 敌机的右边
        let er = this.x + this.width;
        // 敌机的顶边
        let et = this.y;
        // 敌机的底边
        let eb = this.y + this.height

        // 判断是否有碰到
        if (ol > er || or < el || ot > eb || ob < et) {
          // 没碰到
          return false
        } else {
          // 碰到
          return true
        }
      }
      // 敌机碰撞
      collide() {
        // 敌机生命值
        this.life--
        if (this.life === 0) {
          this.live = false
          // 击毁敌机的分数
          score += this.score
        }
      }
    }
    // 小飞机配置项
    const E1 = {
      type: 1,
      width: 57,
      height: 51,
      life: 1,
      score: 1,
      frame: e1,
      minSpeed: 20,
      maxSpeed: 10
    }
    const E2 = {
      type: 2,
      width: 69,
      height: 99,
      life: 5,
      score: 5,
      frame: e2,
      minSpeed: 50,
      maxSpeed: 25
    }
    const E3 = {
      type: 3,
      width: 169,
      height: 258,
      life: 20,
      score: 20,
      frame: e3,
      minSpeed: 100,
      maxSpeed: 100
    }

    // new 一个天空实例
    const sky = new Sky(SKY)
    // console.log(sky);
    // new 一个游戏加载类
    const loading = new Loading(LOADING)
    // new 一个战机
    const hero = new Hero(HERO)
    // new 子弹
    const bullet = new Bullet(BULLET)
    // 分数 score  我方生命值 life
    let score = 0;
    let life = 3;


    // state 表示游戏状态 取值必须是以上定义的五种状态
    let state = START;
    // 为canvas绑定一个点击事件,且他如果是START状态时修改成STARTING状态
    canvas.addEventListener('click', () => {
      if (state === START) {
        state = STARTING
      }
    })
    // 为canvas绑定一个鼠标移动事件,用以移动战机
    canvas.addEventListener('mousemove', (e) => {
      // console.log(e.offsetX, e.offsetY);
      hero.x = e.offsetX - hero.width / 2;
      hero.y = e.offsetY - hero.height / 2;
    })

    // 为canvas绑定鼠标离开事件，用以暂停游戏 RUNNING -> PAUSE
    canvas.addEventListener('mouseleave', () => {
      if (state === RUNNING) {
        state = PAUSE
      }
    })
    // 为canvas绑定鼠标进入事件，用以继续游戏 PAUSE -> RUNNING
    canvas.addEventListener('mouseenter', () => {
      if (state === PAUSE) {
        state = RUNNING
      }
    })

    // 该变量中有所有飞机的实例
    let enemies = []
    //敌机产生的速率
    let ENEMY_CREATE_INTERVAL = 800;
    let ENEMY_LASTTIME = new Date().getTime();
    // 全局函数 规定时间内产生敌机
    function createComponent() {
      const currentTime = new Date().getTime();
      if (currentTime - ENEMY_LASTTIME >= ENEMY_CREATE_INTERVAL) {
        let chance = Math.floor(Math.random() * 100)
        if (chance < 60) {
          // 产生一架小飞机
          enemies.push(new Enemy(E1))
        } else if (chance < 90) {
          // 中飞机
          enemies.push(new Enemy(E2))
        } else {
          // 大飞机
          enemies.push(new Enemy(E3))
        }
        ENEMY_LASTTIME = currentTime
      }
      console.log(enemies);
      // debugger;
    }
    // 飞机碰撞检测函数
    function checkHit() {
      // 遍历所有敌机
      for (let i = 0; i < enemies.length; i++) {
        // 判断敌机与我机碰撞
        if (enemies[i].hit(hero)) {
          enemies[i].collide()
          hero.collide()
        }
        // 第二层遍历所有的子弹
        for (let j = 0; j < hero.bulletList.length; j++) {
          // 用第i个敌机和第j个子弹进行碰撞检测 返回一个布尔类型的值
          enemies[i].hit(hero.bulletList[j]);
          // 如果碰到才做某些事情
          if (enemies[i].hit(hero.bulletList[j])) {
            // 清除这架敌机和子弹
            enemies[i].collide()
            hero.bulletList[j].collide()
          }
        }
      }
    }

    // 全局函数 来判断所有子弹/敌人组件
    function judgeComponent() {
      for (let i = 0; i < hero.bulletList.length; i++) {
        hero.bulletList[i].move();
      }
      for (let i = 0; i < enemies.length; i++) {
        enemies[i].move()
      }
    }
    // 全局函数 来绘制所有的子弹/敌人  / 分数和我方飞机血量
    function paintComponent() {
      for (let i = 0; i < hero.bulletList.length; i++) {
        hero.bulletList[i].paint(context);
      }
      for (let i = 0; i < enemies.length; i++) {
        enemies[i].paint(context)
      }

      // 绘制 score&life面板
      context.font = '20px 微软雅黑'
      context.textAlign = 'left'
      context.fillText('score:' + score, 10, 20)
      context.textAlign = 'right'
      context.fillText('life:' + life, 480 - 20, 20)
    }
    // 全局函数 来销毁所有子弹/敌人
    function destroyComponent() {
      // 我方飞机销毁
      if (hero.destroy) {
        // 飞机销毁，进入游戏结束状态
        state = END
      }
      for (let i = 0; i < hero.bulletList.length; i++) {
        if (hero.bulletList[i].outOfBounds() || hero.bulletList[i].destroy) {
          // 判断有无飞出画布
          hero.bulletList.splice(i, 1)
        }
      }
      for (let i = 0; i < enemies.length; i++) {
        if (enemies[i].destroyed || enemies[i].outOfBounds()) {
          enemies.splice(i, 1)
        }
      }
    }



    // 五个参数
    //    images 加载的图片对象
    // 1、 dX 图片开始绘制的左上角的那个横坐标
    // 2、 dY 图片开始绘制的左上角的那个纵坐标
    // 3、 dWidth 图片在canvas绘制的宽度 （缺省值表示的就是绘制到整张canvas中）
    // 4、 dHeight 图片在canvas绘制的高度
    bg.addEventListener('load', () => {
      setInterval(() => {

        /// 会实时的渲染整个canvas对象， 当游戏进入到不同的状态时渲染不同的内容
        switch (state) {

          case START:
            // console.log('游戏开始');
            // 渲染移动天空背景
            sky.judge()
            sky.paint(context);
            // 飞机大战LOGO
            // 图片原始宽高的获取 naturalWidth&naturalHeight
            let LOGO_X = (480 - copyright.naturalWidth) / 2;
            let LOGO_Y = (700 - copyright.naturalHeight) / 2;
            context.drawImage(copyright, LOGO_X, LOGO_Y)
            break;

          case STARTING:
            // console.log('开始时');
            // 渲染移动天空背景
            sky.judge()
            sky.paint(context);
            // 飞机加载类 loading
            loading.paint(context)
            loading.judge()
            break;

          case RUNNING:
            // console.log('运行时');
            // 渲染移动天空背景
            sky.judge()
            sky.paint(context);
            // 渲染双方飞机
            hero.judge();
            hero.paint(context)
            hero.shoot()
            judgeComponent();
            paintComponent();
            destroyComponent();
            createComponent();
            checkHit();
            break;

          case PAUSE:
            console.log('暂停');
            // 渲染暂停LOGO
            let PAUSE_X = (480 - pause.naturalWidth) / 2;
            let PAUSE_Y = (700 - pause.naturalHeight) / 2;
            context.drawImage(pause, PAUSE_X, PAUSE_Y)
            break;

          case END:
            console.log('结束');
            // 渲染 GAME_OVER
            context.font = 'bold 25px 微软雅黑'
            // 文本水平居中
            context.textAlign = 'center';
            // 垂直居中
            context.textBaseline = 'middle'
            context.fillText('GAME_OVER', 480 / 2, 700 / 2)
            break;
        }

        // sky.judge()
        // sky.paint(context);
      }, 10)
    })
  </script>
</body>

</html>